reactive-property ![Build Status](https://travis-ci.org/curran/reactive-property.svg?branch=master)
![NPM](https://nodei.co/npm/reactive-property.png)
A small library for getter-setter functions that react to changes.
Usage
Create your first property.
var a = ReactiveProperty();
Set its value.
a(5);
Get its value.
a();
Listen for changes.
a.on(function(value){
console.log("The value of 'a' changed to " + value);
});
Cancel your subscription.
function listener(){
console.log("'a' changed!");
}
a.on(listener);
a.off(listener);
a(5);
For convenenience, the listener function is returned from the call to on
, so the following would also work.
var listener = a.on(function (){
console.log("'a' changed!");
});
a.off(listener);
a(5);
Set up method chaining by using a context object.
var my = {
x: ReactiveProperty(5),
y: ReactiveProperty(10)
};
my.x(50).y(100);
That covers the entire API. For more detailed example code, have a look at the tests.
Installation
Install the library by running this command.
npm install reactive-property
Require it in your code like this.
var ReactiveProperty = require("reactive-property");
If you're not using NPM, you can require the script in your HTML like this.
<script src="//curran.github.io/reactive-property/reactive-property-v0.7.0.js"></script>
Or, you can use the minified version (1.5K).
<script src="//curran.github.io/reactive-property/reactive-property-v0.7.0.min.js"></script>
Background
Related works:
After many attempts at building "frameworks" for data visualization (ModelJS, Chiasm), I have learned that abstractions come at a cost. Much to my dismay, I found that when I wanted to apply Chiasm to a particular project, the abstractions had too much surface area and stood in the way of customization. I found myself starting again from raw D3 examples to get projects done, and noticed that as a project grows in complexity organically, the most common need is to listen for changes in state.
This library is my attempt to create a "micro-framework" that eliminates the getter-setter boilerplate code and provides the ability to listen for changes in state. It is intentionally minimal, and no other features are provided. This is to minimize the surface area of this library, and make it appealing for others to adopt as a utility. It is intended for use with D3-based projects, as it helps you generate APIs that follow the D3 convention of chainable getter-setters, but it can be used with other libraries too.
This library is "complete" and fully functional as-is. Aside from bugs or edge cases that come up, no new features will be added to this library. This library is designed to be the foundation of larger systems, and additional functionality should arise by composing this library with other code.
A Note on Immutability
This library is essentially a state container. You will experience totally predictable behavior if you set property values to immutable values. For example, if you want a property to represent a list of things, it may be tempting to do this:
var myList = ReactiveProperty(["a"]);
myList.on(function (value){ console.log("Updated"); });
myList().push("b");
However, in this case, the listener does not get invoked when you push the element onto the array. The same is true for objects.
var myObject = ReactiveProperty({ x: 5 });
myObject.on(function (value){ console.log("Updated"); });
myObject().y = 10;
In order to trigger the listener, you must invoke the setter, like this:
var myList = ReactiveProperty(["a"]);
myList.on(function (value){ console.log("Updated"); });
myList(["a", "b"]);
However, let's say you want to use push
, how could that work?
var myList = ReactiveProperty(["a"]);
myList.on(function (value){ console.log("Updated"); });
myList().push("b");
myList(myList());
In the case of objects:
var myObject = ReactiveProperty({ x: 5 });
myObject.on(function (value){ console.log("Updated"); });
myObject().y = 10;
myObject(myObject());
The above solution does work, but it's actually mutating the value, then passing it into the setter. This is not ideal. In situations like this, you're better off using immutable types such as those provided in Immutable.js for complex value types. Here's what that would look like for arrays (Lists):
var myList = ReactiveProperty(Immutable.List(["a"]););
myList.on(function (value){ console.log("Updated"); });
myList(myList().push("b"));
Here's what using immutable types would look like for arrays (Maps):
var myMap = ReactiveProperty(Immutable.Map({ x: 5 }););
myMap.on(function (value){ console.log("Updated"); });
myMap(myMap().set("y", 10));
This is a good solution, and yields predictable behavior that is easy to reason about.
Contributing
- If you think this project is cool, please give it a star!
- If you end up using this in an example on bl.ocks.org, please let me know by creating a GitHub issue, we can link to your example in this README.
- Feel free to open GitHub issues if you have any questions, comments or suggestions.
I hope you enjoy and benefit from this project!